import * as mapbox from 'mvt-basic-render'

const {WebMercatorTilingScheme, Event, Credit, DefaultProxy, defined, Resource, Math, ImageryLayerFeatureInfo} = Cesium

const baseCanv = document.createElement("canvas");
class MVTImageryProvider {
  /**
   * create a MVTImageryProvider Object
   * @param {MVTImageryProviderOptions} options MVTImageryProvider options as follow:
   * @param {Resource | string | StyleSpecification} options.style - mapbox style object or url Resource.
   * @param {string} options.accessToken - mapbox style accessToken.
   * @param {RequestTransformFunction} options.transformRequest - use transformRequest to modify tile requests.
   * @param {Number} [options.tileSize = 256] - can be 256 or 512. defaults to 256.
   * @param {Number} [options.maximumLevel = 18] - if cesium zoom level exceeds maximumLevel, layer will be invisible, defaults to 18.
   * @param {Number} [options.minimumLevel = 0] - if cesium zoom level belows minimumLevel, layer will be invisible, defaults to 0.
   * @param {Boolean} [options.showCanvas = false] - if show canvas for debug.
   * @param {Boolean} [options.enablePickFeatures = true] - enable pickFeatures or not, defaults to true.
   * @param {Function} options.sourceFilter - sourceFilter is used to filter which source participate in pickFeature process.
   * @param {WebMercatorTilingScheme | GeographicTilingScheme} [options.tilingScheme = WebMercatorTilingScheme] - Cesium tilingScheme, defaults to WebMercatorTilingScheme(EPSG: 3857).
   * @param {Credit} options.credit - A credit contains data pertaining to how to display attributions/credits for certain content on the screen.
   * @example
   * const imageryProvider = new MVTImageryProvider({
        style: 'https://demotiles.maplibre.org/style.json'
      });
   */
  constructor(options) {
    
    this._destroyed = false;
    this.ready = false;
    this.tilingScheme = options.tilingScheme ?? new WebMercatorTilingScheme();
    this.rectangle = this.tilingScheme.rectangle;
    this.tileSize = this.tileWidth = this.tileHeight = options.tileSize || 256;
    this.maximumLevel = options.maximumLevel ?? 18;
    this.minimumLevel = options.minimumLevel ?? 0;
    this.tileDiscardPolicy = void 0;
    this._error = new Event();
    this.credit = new Credit(options.credit || "", false);
    this.proxy = new DefaultProxy("");
    this.hasAlphaChannel = options.hasAlphaChannel ?? true;
    this.sourceFilter = options.sourceFilter;
    this._accessToken = options.accessToken;
    this._enablePickFeatures = options.enablePickFeatures ?? true;
    if (defined(options.style)) {
      this.readyPromise = this._build(options.style, options).then(() => {
        return true;
      });
    }
  }
  /**
   * get mapbox style json obj
   */
  get style() {
    return this._style;
  }
  get isDestroyed() {
    return this._destroyed;
  }
  /**
   * Gets an event that will be raised if an error is encountered during processing.
   * @memberof GeoJsonDataSource.prototype
   * @type {Event}
   */
  get errorEvent() {
    return this._error;
  }
  async _build(url, options = {}) {
    const style = await this._preLoad(url);
    this._style = style;
    this.mapboxRenderer = new mapbox.BasicRenderer({
      style,
      canvas: baseCanv,
      token: options.accessToken,
      transformRequest: options.transformRequest
    });
    if (options.showCanvas) {
      this.mapboxRenderer.showCanvasForDebug();
    }
    await this.mapboxRenderer._style.loadedPromise;
    this.readyPromise = Promise.resolve(true);
    this.ready = true;
  }
  static async fromUrl(url, options = {}) {
    const provider = new MVTImageryProvider(options);
    await provider._build(url, options);
    return provider;
  }
  _preLoad(data) {
    let promise = data;
    if (typeof data === "string") {
      data = new Resource({
        url: data
      });
    }
    if (data instanceof Resource) {
      const prefix = "https://api.mapbox.com/";
      if (data.url.startsWith("mapbox://"))
        data.url = data.url.replace("mapbox://", prefix);
      if (this._accessToken)
        data.appendQueryParameters({
          access_token: this._accessToken
        });
      promise = data.fetchJson();
    }
    return Promise.resolve(promise).catch((error) => {
      this._error.raiseEvent(error);
      throw error;
    });
  }
  _createTile() {
    const canv = document.createElement("canvas");
    canv.width = this.tileSize;
    canv.height = this.tileSize;
    canv.style.imageRendering = "pixelated";
    return canv;
  }
  /**
   * reset tile cache
   */
  _resetTileCache() {
    Object.values(this.mapboxRenderer._style.sourceCaches).forEach((cache) => cache._tileCache.reset());
  }
  _getTilesSpec(coord, source) {
    const { x, y, level } = coord;
    const TILE_SIZE = this.tileSize;
    const ret = [];
    const maxX = this.tilingScheme.getNumberOfXTilesAtLevel(level) - 1;
    const maxY = this.tilingScheme.getNumberOfYTilesAtLevel(level) - 1;

    for (let xx = -1; xx <= 1; xx++) {
      let newx = x + xx;
      if (newx < 0)
        newx = maxX;
      if (newx > maxX)
        newx = 0;
      for (let yy = -1; yy <= 1; yy++) {
        let newy = y + yy;
        if (newy < 0 || newy > maxY)
          continue;
        ret.push({
          source,
          z: level+2,
          x: newx,
          y: newy,
          left: 0 + xx * TILE_SIZE,
          top: 0 + yy * TILE_SIZE,
          size: TILE_SIZE
        });
      }
    }
    return ret;
  }
  requestImage(x, y, level, releaseTile = true) {
    if (level < this.minimumLevel || level > this.maximumLevel)
      return void 0;
    this.mapboxRenderer.filterForZoom(level);
    const tilesSpec = this.mapboxRenderer.getVisibleSources(level).reduce((a, s) => a.concat(this._getTilesSpec({ x, y, level }, s)), []);
    return new Promise((resolve, reject) => {
      const canv = this._createTile();
      const ctx = canv.getContext("2d");
      if (ctx)
        ctx.globalCompositeOperation = "copy";
      const renderRef = this.mapboxRenderer.renderTiles(
        ctx,
        {
          srcLeft: 0,
          srcTop: 0,
          width: this.tileSize,
          height: this.tileSize,
          destLeft: 0,
          destTop: 0
        },
        tilesSpec,
        (err) => {
          if (typeof err === "string" && !err.endsWith("tiles not available")) {
            reject(void 0);
          } else if (releaseTile) {
            renderRef.consumer.ctx = null;
            resolve(canv);
            this.mapboxRenderer.releaseRender(renderRef);
            this._resetTileCache();
          } else {
            resolve(renderRef);
          }
        }
      );
    });
  }
  pickFeatures(x, y, zoom, longitude, latitude) {
    var _a;
    if (!this._enablePickFeatures)
      return void 0;
    return (_a = this.requestImage(x, y, zoom, false)) == null ? void 0 : _a.then((renderRef) => {
      let targetSources = this.mapboxRenderer.getVisibleSources(zoom);
      targetSources = this.sourceFilter ? this.sourceFilter(targetSources) : targetSources;
      const queryResult = [];
      const lng = Math.toDegrees(longitude);
      const lat = Math.toDegrees(latitude);
      targetSources.forEach((s) => {
        const featureInfo = new ImageryLayerFeatureInfo();
        featureInfo.data = this.mapboxRenderer.queryRenderedFeatures({
          source: s,
          renderedZoom: zoom,
          lng,
          lat,
          tileZ: zoom
        });
        const name = Object.keys(featureInfo.data)[0];
        featureInfo.name = name;
        const properties = featureInfo.data[name];
        if (properties) {
          featureInfo.configureDescriptionFromProperties((properties == null ? void 0 : properties.length) === 1 ? properties[0] : properties);
          queryResult.push(featureInfo);
        }
      });
      renderRef.consumer.ctx = void 0;
      this.mapboxRenderer.releaseRender(renderRef);
      this._resetTileCache();
      return queryResult.length ? queryResult : void 0;
    });
  }
  destroy() {
    this.mapboxRenderer._cancelAllPendingRenders();
    this._resetTileCache();
    this._destroyed = true;
  }
}

export { MVTImageryProvider };
